﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using CI.Misc;
using CI.Organism;
using CI.PW.Controls;
using CI.PW.Models;
using GraphSharp.Controls;
using QuickGraph;

namespace CI.PW.ViewModels
{
    ///<summary>
    /// Neurnal network view model
    ///</summary>
    public class NNetViewModel : DataModel, IDisposable
    {

        #region properties
        private readonly GraphLayout _graphLayout;

        /// <summary>
        /// graph
        /// </summary>
        public BidirectionalGraph<object, IEdge<object>> GraphToVisualize
        {
            get { return _graphToVisualize; }
            private set
            {
                if (_graphToVisualize == value)
                    return;

                _graphToVisualize = value;
                RaisePropertyChanged("GraphToVisualize");

                if (value != null)
                    _graphLayout.Relayout();
            }
        }
        private BidirectionalGraph<object, IEdge<object>> _graphToVisualize;
        private static readonly Dictionary<object, object> VertexLists = new Dictionary<object, object>();

        private IOrganism _currentOrganism;
        public IOrganism CurrentOrganism { get { return _currentOrganism; } }

        private delegate void HighLightVertexDelegate(object vertex);
        private readonly Action<IOrganism> _drawGraph;
        private readonly Action _unHighLightAction;
        private readonly HighLightVertexDelegate _highLightVertexHandler;
        private bool _drawRequested;

        private readonly TimeSpan _refreshRate = new TimeSpan(0, 0, 0, 0, 250);
        private Timer _clearTimer;

        private bool _highLightingEnabled;

        private NeuralNetworkDisplayMode _neuralNetworkDisplayMode;
        public NeuralNetworkDisplayMode NeuralNetworkDisplayMode
        {
            get { return _neuralNetworkDisplayMode; }
            set
            {

                _simEnv.TraceLogger.LogDebug("NeuralNetworkDisplayMode - Start");
                var switchingMode = _neuralNetworkDisplayMode != value;

                if (switchingMode)
                {
                    RemoveVertexAndNeuralHandlers();
                }

                lock (SyncObject)
                {
                    _neuralNetworkDisplayMode = value;

                    switch (value)
                    {
                        case NeuralNetworkDisplayMode.Off:
                            _dispatcherPriority = DispatcherPriority.Inactive;
                            break;
                        case NeuralNetworkDisplayMode.Indicitive:
                            _dispatcherPriority = DispatcherPriority.Background;
                            break;
                        case NeuralNetworkDisplayMode.RealTime:
                            _dispatcherPriority = DispatcherPriority.Send;
                            break;
                    }

                }

                EvaluateHighLighting();

                if (switchingMode)
                {
                    if (_currentOrganism != null)
                        StartDisplaySelectedOrganism(_currentOrganism);
                }

                _simEnv.TraceLogger.LogDebug("NeuralNetworkDisplayMode - End");
            }
        }

        private DispatcherPriority _dispatcherPriority = DispatcherPriority.Background;

        private static readonly object SyncObject = new object();

        #endregion

        private readonly SimEnv _simEnv = SimEnv.Instance;

        ///<summary>
        /// ctor with graphlayout
        ///</summary>
        ///<param name="graphLayout"></param>
        public NNetViewModel(GraphLayout graphLayout)
        {
            _graphLayout = graphLayout;


            _highLightVertexHandler = new HighLightVertexDelegate(HightLightVertex);

            _drawGraph = new Action<IOrganism>(DrawGraph);
            _unHighLightAction = new Action(UnhighlightCallBack);

            SimEnv.Instance.OnSetSelectedOrganismEvent += DisplaySelectedItem;
        }

        /// <summary>
        /// Display the selected item
        /// </summary>
        /// <param name="organism"></param>
        private void DisplaySelectedItem(IOrganism organism)
        {
            _simEnv.TraceLogger.LogDebug("DisplaySelectedItem NeuralNetworkWPF - Start");

            // make sure their is an organism
            if ((organism != null) && (organism != _currentOrganism))
            {
                StartDisplaySelectedOrganism(organism);
            }

            _simEnv.TraceLogger.LogDebug("DisplaySelectedItem NeuralNetworkWPF - Finish");
        }

        private void StartDisplaySelectedOrganism(IOrganism organism)
        {
            if ((_currentOrganism != null) && (_currentOrganism.NeuralSystem != null))
                RemoveVertexAndNeuralHandlers();

            _currentOrganism = organism;

            // if no brain hide it
            if (_currentOrganism.NeuralSystem == null)
            {
                GraphToVisualize = new BidirectionalGraph<object, IEdge<object>>();
                _highLightingEnabled = false;
            }
            else
            {
                StartGraphDrawing(_currentOrganism);
            }
        }

        private void AddNeuralSystemHandlers()
        {
            _currentOrganism.NeuralSystem.NeuralSystemUpdateHandler += NeuralSystemUpdateHandler;

            if (_clearTimer != null)
                _clearTimer.Dispose();

            if (NeuralNetworkDisplayMode != NeuralNetworkDisplayMode.Indicitive)
            {
                _currentOrganism.NeuralSystem.NotifyEvaluationStop += NeuralSystemEvaluationStop;
            }
            else
            {
                _clearTimer = new Timer((obj => NeuralSystemEvaluationStop()), null, _refreshRate, _refreshRate);
            }
        }


        private void RemoveNeuralSystemHandlers()
        {
            if (_currentOrganism == null || _currentOrganism.NeuralSystem == null) return;

            _currentOrganism.NeuralSystem.NeuralSystemUpdateHandler -= NeuralSystemUpdateHandler;
            _currentOrganism.NeuralSystem.NotifyEvaluationStop -= NeuralSystemEvaluationStop;
        }

        private void NeuralSystemUpdateHandler()
        {
            _simEnv.TraceLogger.LogDebug("NeuralSystemUpdateHandler");
            StartGraphDrawing(_currentOrganism);
        }

        private void StartGraphDrawing(IOrganism organism)
        {
            if (_drawRequested)
                return;

            _drawRequested = true;

            _simEnv.TraceLogger.LogDebug("StartGraphDrawing");
            UIDispatcher.BeginInvoke(DispatcherPriority.Background, _drawGraph, organism);
        }

        private void DrawGraph(IOrganism organism)
        {
            _drawRequested = false;
            _simEnv.TraceLogger.LogDebug("DrawGraph NeuralNetworkWPF - Start");

            GraphToVisualize = null;
            VertexLists.Clear();
            _highLightingEnabled = false;

            RemoveVertexAndNeuralHandlers();

            if (_graphLayout.Visibility != Visibility.Visible)
                _graphLayout.Visibility = Visibility.Visible;

            var g = NeuralNetworkDraw.BuildGraph(organism);

            AddNeuralSystemHandlers();

            if (g.EdgeCount == 0)
            {
                GraphToVisualize = new BidirectionalGraph<object, IEdge<object>>();
                EvaluateHighLighting();
                return;
            }

            GraphToVisualize = g;

            if (NeuralNetworkDisplayMode != NeuralNetworkDisplayMode.Off)
                AddHandlers();

            EvaluateHighLighting();

            _simEnv.TraceLogger.LogDebug("DrawGraph NeuralNetworkWPF - End");
        }

        private void RemoveVertexAndNeuralHandlers()
        {
            RemoveHandlers();
            RemoveNeuralSystemHandlers();
        }

        private void EvaluateHighLighting()
        {
            if (GraphToVisualize != null && GraphToVisualize.VertexCount > 0 && GraphToVisualize.EdgeCount > 0
                && NeuralNetworkDisplayMode != NeuralNetworkDisplayMode.Off)
                _highLightingEnabled = true;
            else
                _highLightingEnabled = false;
        }

        private void RemoveHandlers()
        {
            if (GraphToVisualize == null)
                return;

            foreach (var vertex in GraphToVisualize.Vertices)
            {
                var vertexModel = ((VertexModel)vertex);
                vertexModel.VertexActivation -= HighLightVertex;
            }
        }

        private void AddHandlers()
        {
            foreach (var vertex in GraphToVisualize.Vertices)
            {
                var vertexModel = ((VertexModel)vertex);
                vertexModel.VertexActivation += HighLightVertex;
            }
        }

        private void HighLightVertex(object vertex)
        {
            if (!_highLightingEnabled)
                return;

            try
            {
                _simEnv.TraceLogger.LogDebug("HighLightVertex - Start");

                lock (SyncObject)
                {
                    if (VertexLists.ContainsKey(vertex))
                        return;

                    VertexLists.Add(vertex, true);
                }

                if (_highLightingEnabled)
                {
                    if (NeuralNetworkDisplayMode == NeuralNetworkDisplayMode.RealTime)
                        UIDispatcher.Invoke(_dispatcherPriority, _highLightVertexHandler, vertex);
                    else
                        UIDispatcher.BeginInvoke(_dispatcherPriority, _highLightVertexHandler, vertex);
                }

                _simEnv.TraceLogger.LogDebug("HighLightVertex - End");
            }
            catch (Exception exception)
            {
                _simEnv.TraceLogger.LogWarn("Exception trying to highlight vertex " + vertex + " with exception " + exception.Message);
            }
        }

        private void HightLightVertex(object vertex)
        {
            try
            {
                if (_highLightingEnabled && GraphToVisualize.ContainsVertex(vertex))
                {
                    _graphLayout.HighlightVertex(vertex, "Source");
                }
            }
            catch (Exception exception)
            {

                _simEnv.TraceLogger.LogWarn("Exception trying to highlight and remove vertex " + vertex +
                                            " with exception " +
                                            exception.Message);
            }
        }

        private void NeuralSystemEvaluationStop()
        {
            _simEnv.TraceLogger.LogDebug("UnHighLightVertex - Start");

            lock (SyncObject)
            {
                if (VertexLists.Count == 0) return;

                VertexLists.Clear();
            }

            if (_highLightingEnabled)
            {
                if (NeuralNetworkDisplayMode == NeuralNetworkDisplayMode.RealTime)
                    UIDispatcher.Invoke(_dispatcherPriority, _unHighLightAction);
                else
                    UIDispatcher.BeginInvoke(_dispatcherPriority, _unHighLightAction);
            }

            _simEnv.TraceLogger.LogDebug("UnHighLightVertex - End");
        }

        private void UnhighlightCallBack()
        {
            foreach (var vertex in new List<object>(_graphLayout.HighlightedVertices))
            {
                _graphLayout.RemoveHighlightFromVertex(vertex);
            }
        }

        public void Dispose()
        {
            _simEnv.TraceLogger.LogDebug("NNetViewModel Dispose - Start");

            _highLightingEnabled = false;
            NeuralNetworkDisplayMode = NeuralNetworkDisplayMode.Off;
            RemoveVertexAndNeuralHandlers();
            UIDispatcher.InvokeShutdown();

            _simEnv.TraceLogger.LogDebug("NNetViewModel Dispose - End");
        }
    }
}
